﻿using System;
using System.ComponentModel;

using Spencen.Mobile.UI.Easings;

namespace Spencen.Mobile.UI.Animations
{
    public abstract class Animation
    {
        public IEasingFunction EasingFunction { get; set; }
        public TimeSpan Duration { get; set; }
        public object Target { get; protected set; }
        public PropertyDescriptor TargetProperty { get; protected set; }
        public float PercentageComplete { get; private set; }
        public Action<Animation> Completed { get; set; }

        protected long? StartTick { get; private set; }
        protected long? EndTick { get; private set; }
        protected float AdjustedPercentageComplete { get; set; }

        public Animation( object target, string propertyName, TimeSpan duration )
        {
            Target = target;
            Duration = duration;
            TargetProperty = TypeDescriptor.GetProperties( target ).Find( propertyName, false );
            if ( TargetProperty == null )
                throw new ArgumentException( string.Format( "Property {0} not found on type {1}.", propertyName, target.GetType().Name ), "propertyName" );

            if ( TargetProperty.IsReadOnly )
                throw new ArgumentException( string.Format( "Property {0} on type {1} cannot be animated because it is read-only.", propertyName, target.GetType().Name ), "propertyName" );
        }

        public void Start()
        {
            StartTick = Environment.TickCount;
            GetOriginalValue();
        }

        public void Stop()
        {
            EndTick = Environment.TickCount;
            PercentageComplete = 1.0f;
            if ( Completed != null )
                Completed( this );
        }

        public void Step()
        {
            if ( !StartTick.HasValue )
                throw new InvalidOperationException( "The animation must be started before a Step can be performed." );

            long currentTick = Environment.TickCount;
            long currentTime = currentTick - StartTick.Value;
            PercentageComplete = currentTime / (float)Duration.TotalMilliseconds;

            if ( PercentageComplete >= 1.0f )
                Stop();

            if ( EasingFunction != null )
                AdjustedPercentageComplete = EasingFunction.EasedValue( PercentageComplete );
            else
                AdjustedPercentageComplete = PercentageComplete;

            ApplyCurrent();
        }

        protected abstract void GetOriginalValue();
        protected abstract void ApplyCurrent();

        public bool InProgress { get { return StartTick.HasValue && !EndTick.HasValue; } }
        public bool IsStarted { get { return StartTick.HasValue; } }
    }

    public abstract class Animation<T> : Animation
    {
        public Animation( object target, string propertyName, TimeSpan duration )
            : base( target, propertyName, duration )
        {
        }

        public Animation( object target, string propertyName, TimeSpan duration, T finalValue )
            : this( target, propertyName, duration )
        {
            FinalValue = finalValue;
        }

        public T FinalValue { get; set; }
        protected T OriginalValue { get; set; }
        public Func<object, T, T> Constraint { get; set;  } 

        public abstract T CurrentValue
        {
            get;
        }

        protected override void ApplyCurrent()
        {
            var constrainedValue = CurrentValue;
            if ( Constraint != null )
                constrainedValue = Constraint( Target, CurrentValue );
            else
                constrainedValue = CurrentValue;

            TargetProperty.SetValue( Target, constrainedValue );
        }

        protected override void GetOriginalValue()
        {
            OriginalValue = (T)TargetProperty.GetValue( Target );
        }
    }
}
